#include "Merger.h"

using namespace std;

// CONSTRUCTOR
Merger::Merger(int nroArchivosTemporales,
		int nroDocumentos, int mergeBufferSize) {
	
	this->nroArchivosTemporales = nroArchivosTemporales;
	this->nroDocumentos = nroDocumentos;
	this->mergeBufferSize = mergeBufferSize;
	this->outIndex = NULL;
   	this->inIndex1 = NULL;
   	this->inIndex2 = NULL;
   	this->outDocFrec = NULL;
   	this->inDocFrec1 = NULL;
   	this->inDocFrec2 = NULL;
   	this->outLexico = NULL;
   	this->inLexico1 = NULL;
   	this->inLexico2 = NULL;
   	
   	inIndexDocs = new AlmacenamientoDocs(MODIFICAR);
   	vectorNormas = new float[nroDocumentos];
   	
   	//inicializacion del vector con ceros
	for (int i = 0; i < nroDocumentos; i++)
		vectorNormas[i] = 0;
   	
   	this->ultimo = false;
   	
   	AlmacenamientoIndice::resetearCodigoArchivo();
   	AlmacenamientoDocFrec::resetearCodigoArchivo();
   	Lexico::resetearCodigoArchivo();
}

// DESTRUCTOR
Merger::~Merger() {
	delete inIndexDocs;
	delete [] vectorNormas;
}

void Merger::merge() {

	string extension;
	int k, nroPares;
	bool impar;

	// no hay indices temporales, no hay que hacer nada.
	if (this->nroArchivosTemporales == 0)
		return;
	else if (this->nroArchivosTemporales == 1) {

		// Renombra el archivo
		inIndex1 = new AlmacenamientoIndice(ABRIR, 0, true);
		inLexico1 = new Lexico(ABRIR, 0, true);
		inDocFrec1 = new AlmacenamientoDocFrec(ABRIR, 0, true);

		inIndex1->renombrarArchivoFinal();
		inLexico1->renombrarArchivoFinal();

		inIndex1->abrirArchivo();
		inLexico1->abrirArchivo();
		inDocFrec1->abrirArchivo();

		outDocFrec = new AlmacenamientoDocFrec(CREAR, 0);
		outDocFrec->abrirArchivo();

		archivoTemporalUnico();

		inDocFrec1->setDestroy();
	
		delete inIndex1;
		delete inLexico1;
		delete inDocFrec1;

		delete outDocFrec;

	} else {

		// Tenemos 2 o mas archivos.
		// Se hace el Merge de a 2, hasta que queden 2, y luego con esos dos se generan los archivos finales
		while (this->nroArchivosTemporales >= 2) {

			nroPares = this->nroArchivosTemporales / 2;

			ultimo = (nroArchivosTemporales == 2);

			for (k = 0; k < nroPares; k++) {
				instanciarObjetosAlmacenamiento();
				Merge();
				setearDestruiblesAlmacenamiento();
				borrarObjetosAlmacenamiento();
			}

			// Si el nro de archivos es impar, el ultimo solo es renombrado.
			if ( 2*nroPares < this->nroArchivosTemporales ) {
			
				impar = true;
				outIndex = new AlmacenamientoIndice(ABRIR, 0, true);
				outIndex->renombrarArchivoANivel(NIVEL_1);
				delete outIndex;
	
				outDocFrec = new AlmacenamientoDocFrec(ABRIR, 0, true);
				outDocFrec->renombrarArchivoANivel(NIVEL_1);
				delete outDocFrec;
	
				outLexico = new Lexico(ABRIR, 0, true);
				outLexico->renombrarArchivoANivel(NIVEL_1);
				delete outLexico;
			} else
				impar = false;

			// obtenemos el nuevo numero de temporales
			this->nroArchivosTemporales = nroPares;

			if (impar)
				this->nroArchivosTemporales++;

			//resetamos las variables estáticas para empezar desde cero la numeración
			AlmacenamientoIndice::resetearCodigoArchivo();
			AlmacenamientoIndice::resetearCodigoArchivoNivel1();
			AlmacenamientoDocFrec::resetearCodigoArchivo();
			AlmacenamientoDocFrec::resetearCodigoArchivoNivel1();
			Lexico::resetearCodigoArchivo();
			Lexico::resetearCodigoArchivoNivel1();

			//renombramos los archivos para dejarlos en nivel 0
			for (k = 0; k < nroArchivosTemporales; k++) {
				outIndex = new AlmacenamientoIndice(ABRIR, 1, true);
				outIndex->renombrarArchivoANivel(NIVEL_0);
				outLexico = new Lexico(ABRIR, 1, true);
				outLexico->renombrarArchivoANivel(NIVEL_0);
				outDocFrec = new AlmacenamientoDocFrec(ABRIR, 1, true);
				outDocFrec->renombrarArchivoANivel(NIVEL_0);
				delete outIndex;
				delete outLexico;
				delete outDocFrec;
			}

			//resetamos las variables estáticas para empezar desde cero la numeración
			AlmacenamientoIndice::resetearCodigoArchivo();
			AlmacenamientoIndice::resetearCodigoArchivoNivel1();
			AlmacenamientoDocFrec::resetearCodigoArchivo();
			AlmacenamientoDocFrec::resetearCodigoArchivoNivel1();
			Lexico::resetearCodigoArchivo();
			Lexico::resetearCodigoArchivoNivel1();

			// Si los archivos temporales son 2, se hizo el merge final -> los renombramos
			if (this->nroArchivosTemporales == 1) {
	
				outIndex = new AlmacenamientoIndice(ABRIR, 0, true);
				outIndex->renombrarArchivoFinal();
				delete outIndex;
				outLexico = new Lexico(ABRIR, 0, true);
				outLexico->renombrarArchivoFinal();
				delete outLexico;
				outDocFrec = new AlmacenamientoDocFrec(ABRIR, 0, true);
				outDocFrec->renombrarArchivoFinal();
				delete outDocFrec;
			}
		} //end while
	} //end else
	guardarNormaDocumentos();
} //end function


/**************** va llenando el buffer con RegistroIndice ******************/
int Merger::FillBuffer(AlmacenamientoIndice * inIndex, Lexico * lexico, 
					   AlmacenamientoDocFrec * inDocFrec, MergeBuffer & buffer, 
					   bool & moreData, RegistroIndiceMemoria * & regIndex,
					   int & nroRegistro)
{
	int k = 0;

   	while (moreData && (k < buffer.getBufferSize()))
   	{
   		buffer.setRegistro(k, regIndex);
      	regIndex = obtenerRegistroIndiceMemoria(nroRegistro, inIndex, lexico, inDocFrec);

      	if (regIndex == NULL)
      		moreData = false;
      	else {
        	moreData = true;
      	}
		k++;
		nroRegistro++;
   	}
	
	return k;
}

/******** es el verdadero metodo que hace el merge, comparando registros *********/
void Merger::Merge() {

	int index1, index2, index3 = 0; // los indices de los buffers y el de salida
	int k, resultCompare; // un "iterador" de un for y el resultado del strcmp
	int numRegs1, numRegs2; // numero de registros leidos en fill
	RegistroIndiceMemoria * regIndex1, * regIndex2;
	bool haveData1, haveData2, moreData1, moreData2;
	MergeBuffer buffer1(this->mergeBufferSize), buffer2(this->mergeBufferSize), buffer3(this->mergeBufferSize);
	char * palabra1, * palabra2;
	int nroRegistro1 = 0, nroRegistro2 = 0; //el nro de reg a leer

	// Tratando de llenar el buffer desde InIndex1.
	regIndex1 = obtenerRegistroIndiceMemoria(nroRegistro1, inIndex1, inLexico1, inDocFrec1);
	if (regIndex1 == NULL)
		moreData1 = false;
	else {
		nroRegistro1++;
		moreData1 = true;
		numRegs1 = FillBuffer(inIndex1, inLexico1, inDocFrec1, buffer1, moreData1, regIndex1, nroRegistro1);
	}

	// Tratando de llenar el buffer desde InIndex2.
	regIndex2 = obtenerRegistroIndiceMemoria(nroRegistro2, inIndex2, inLexico2, inDocFrec2);
	if (regIndex2 == NULL)
		moreData2 = false;
	else {
		nroRegistro2++;
		moreData2 = true;
		numRegs2 = FillBuffer(inIndex2, inLexico2, inDocFrec2, buffer2, moreData2, regIndex2, nroRegistro2);
	}

	index1 = 0;
	index2 = 0;
	// Cada haveData indica si hay mas info "no mergeada". Esta data puede estar en
	// regIndex, el correspondiente buffer, or ambos.
	haveData1 = moreData1 || (index1 < numRegs1);
	haveData2 = moreData2 || (index2 < numRegs2);

	// mientras los dos archivos tengan datos, los procesa
	while (haveData1 && haveData2) {

		if (index1 == numRegs1) {
			numRegs1 = FillBuffer(inIndex1, inLexico1, inDocFrec1, buffer1, moreData1, regIndex1, nroRegistro1);
			if (numRegs1 == 0)
				break;   // No hay mas info en inIndex1.
			else
				index1 = 0;
		}

		if (index2 == numRegs2) {
			numRegs2 = FillBuffer(inIndex2, inLexico2, inDocFrec2, buffer2, moreData2, regIndex2, nroRegistro2);
			if (numRegs2 == 0)
				break;   // No hay mas info en inIndex2.
			else
				index2 = 0;
		}

		palabra1 = buffer1[index1]->getTermino();
		palabra2 = buffer2[index2]->getTermino();
	
		resultCompare = strcmp (palabra1, palabra2);
	
		if (resultCompare == 0) {   // copiar ambos registros.
			tListaDocDistancias * listaMergeada;
			RegistroIndiceMemoria * registroMergeado = new RegistroIndiceMemoria();
			listaMergeada = mergeListaDocFrec(
							buffer1[index1]->getListaDocumentosDistancias(),
							buffer2[index2]->getListaDocumentosDistancias());
							registroMergeado->setListaDocumentosDistancias(listaMergeada);
							registroMergeado->setTermino(palabra1);
							registroMergeado->setFrecuenciaAbsoluta(listaMergeada->size()
			);

			Copy(registroMergeado, buffer3, index3);
	
			delete buffer1[index1];
			delete buffer2[index2];

			index1++;
			index2++;
		}
		else if (resultCompare < 0) {  // Copiamos el 1er item.
			Copy(buffer1[index1], buffer3, index3);
			index1++;
		}
		else {   // resultado > 0, asi que copiamos el 2do item.
			Copy(buffer2[index2], buffer3, index3);
			index2++;
		}
	
		haveData1 = moreData1 || (index1 < numRegs1);
		haveData2 = moreData2 || (index2 < numRegs2);
	}

	// Manejo de información que quede en alguno de los archivos
	while (haveData1) {
		if (index1 == numRegs1) {
			numRegs1 = FillBuffer(inIndex1, inLexico1, inDocFrec1, buffer1, moreData1, regIndex1, nroRegistro1);
			if (numRegs1 == 0)
				break;   // No hay mas info en inIndex1.
			else
				index1 = 0;
		}

		Copy(buffer1[index1], buffer3, index3);

		index1++;
		haveData1 = moreData1 || (index1 < numRegs1);
	}

	while (haveData2) {
		if (index2 == numRegs2) {
			numRegs2 = FillBuffer(inIndex2, inLexico2, inDocFrec2, buffer2, moreData2, regIndex2, nroRegistro2);
			if (numRegs2 == 0)
				break;   // No hay mas info en inIndex2.
			else
				index2 = 0;
		}

		Copy(buffer2[index2], buffer3, index3);

		index2++;
		haveData2 = moreData2 || (index2 < numRegs2);
	}

	// escribimos lo que quede en el buffer.
	for (k = 0; k < index3; k++) {
		guardar(buffer3[k]);
	}
}

/**** copia regIndex en el buffer de salida y si este se llena, lo manda a disco ****/ 
void Merger::Copy(RegistroIndiceMemoria* regIndex, MergeBuffer & buffer, int & index) {

	int k;

	// Sin lugar en el buffer, lo bajamos al archivo
	if (index == buffer.getBufferSize()) {

		for (k = 0; k < buffer.getBufferSize(); k++) {
			guardar(buffer[k]);
		}

		index = 0; //cuando se bajo a disco, se pasa el index del buffer a cero
	}

	if (ultimo) {
		regIndex->setListaDocumentosDistancias(transformarFrecuenciaEnPeso(regIndex->getListaDocumentosDistancias()));
	}
	buffer.setRegistro(index, regIndex);
	index++;
}

//Funciones para hacer todo más modular

void Merger::guardar(RegistroIndiceMemoria* regMem) {

	RegistroIndice* reg = new RegistroIndice();
	int offsetDocFrec;

	reg->setFrecuencia(regMem->getFrecuenciaAbsoluta());
	offsetDocFrec = outDocFrec->escribir(regMem->getListaDocumentosDistancias(), ultimo);
	reg->setOffsetDocs(offsetDocFrec);
	reg->setOffsetLexico(outLexico->obtenerOffsetActual());

	outLexico->escribirTermino(regMem->getTermino());
	outIndex->escribirRegistroIndice(reg);

	// invoco al metodo Java para actualizar el arbol
	if(ultimo) {
		Entorno::Instance().updateTree(regMem->getTermino(), offsetDocFrec, regMem->getFrecuenciaAbsoluta());
	}

	delete reg;
	delete regMem;
}

// maneja el caso de cuando es solo un indice parcial
void Merger::archivoTemporalUnico() {
	
	RegistroIndiceMemoria * regIndex1;
	bool moreData1;
	MergeBuffer buffer1(this->mergeBufferSize), buffer3(this->mergeBufferSize);
	int nroRegistro1 = 0; //el nro de reg a leer

	// Tratando de llenar el buffer desde InIndex1.
	regIndex1 = obtenerRegistroIndiceMemoria(nroRegistro1, inIndex1, inLexico1, inDocFrec1);
	if (regIndex1 == NULL)
		moreData1 = false;
	else {
		nroRegistro1++;
		moreData1 = true;
		regIndex1->setListaDocumentosDistancias(transformarFrecuenciaEnPeso(regIndex1->getListaDocumentosDistancias()));
		outDocFrec->escribir(regIndex1->getListaDocumentosDistancias(), true);
		delete regIndex1;
	}

	while (moreData1) {

		regIndex1 = obtenerRegistroIndiceMemoria(nroRegistro1, inIndex1, inLexico1, inDocFrec1);
		if (regIndex1 == NULL)
			moreData1 = false;
		else {
			moreData1 = true;
			regIndex1->setListaDocumentosDistancias(transformarFrecuenciaEnPeso(regIndex1->getListaDocumentosDistancias()));
			outDocFrec->escribir(regIndex1->getListaDocumentosDistancias(), true);	
		}
		nroRegistro1++;
		delete regIndex1;
	}
}

/************** crea los objetos que se utilizarán en el merge ***********/
void Merger::instanciarObjetosAlmacenamiento() {
	inIndex1 = new AlmacenamientoIndice(ABRIR, 0, true);
	inIndex2 = new AlmacenamientoIndice(ABRIR, 0, true);
	outIndex = new AlmacenamientoIndice(CREAR, 1, true);
	inIndex1->abrirArchivo();
	inIndex2->abrirArchivo();
	outIndex->abrirArchivo();
	inDocFrec1 = new AlmacenamientoDocFrec(ABRIR, 0, true);
	inDocFrec2 = new AlmacenamientoDocFrec(ABRIR, 0, true);
	outDocFrec = new AlmacenamientoDocFrec(CREAR, 1, true);
	inDocFrec1->abrirArchivo();
	inDocFrec2->abrirArchivo();
	outDocFrec->abrirArchivo();
	inLexico1 = new Lexico(ABRIR, 0, true);
	inLexico2 = new Lexico(ABRIR, 0, true);
	outLexico = new Lexico(CREAR, 1, true);
	inLexico1->abrirArchivo();
	inLexico2->abrirArchivo();
	outLexico->abrirArchivo();
}

/* hace que los archivos indicados, además de cerrarse se borren 
 * al llamar al destructor */
void Merger::setearDestruiblesAlmacenamiento() {
	inIndex1->setDestroy();
	inIndex2->setDestroy();
	inDocFrec1->setDestroy();
	inDocFrec2->setDestroy();
	inLexico1->setDestroy();
	inLexico2->setDestroy();
}

// llama a los destructores de los objetos AlmacenamientoIndice y DocFrec, y Lexico
void Merger::borrarObjetosAlmacenamiento() {
	delete inIndex1;
	delete inIndex2;
	delete outIndex;
	delete inDocFrec1;
	delete inDocFrec2;
	delete outDocFrec;
	delete inLexico1;
	delete inLexico2;
	delete outLexico;
}

/*********** Se arma un RegistroIndiceMemoria a partir de RegistroIndice ***********/
RegistroIndiceMemoria * Merger::obtenerRegistroIndiceMemoria(
	int nroRegistro,
	AlmacenamientoIndice* inIndex,
	Lexico* lexico,
	AlmacenamientoDocFrec* inDocFrec ) {
	
	RegistroIndice* regIndex = inIndex->leerRegistroIndice(nroRegistro);
	
	if (regIndex == NULL)
		return NULL;
	
	RegistroIndiceMemoria* resultado = new RegistroIndiceMemoria();

	resultado->setFrecuenciaAbsoluta(regIndex->getFrecuencia());
	resultado->setListaDocumentosDistancias(inDocFrec->leer(regIndex->getOffsetDocs(), regIndex->getFrecuencia(), false));
	
	char* temp = lexico->obtenerTermino(regIndex->getOffsetLexico());
	resultado->setTermino(temp);

	delete []temp;
	delete regIndex;
	
	return resultado;
}

// recibe dos listas de documento-frecuencia y devuelva el merge de las dos
tListaDocDistancias * Merger::mergeListaDocFrec(tListaDocDistancias * lista1, tListaDocDistancias * lista2) {
	
	tListaDocDistancias * resultado = new tListaDocDistancias();
	RegistroDistancias * reg1;
	RegistroDistancias * reg2;
	RegistroDistancias * regNew;
	
	tListaDocDistancias::iterator iter1 = lista1->begin();
	tListaDocDistancias::iterator iter2 = lista2->begin();
	
	while ((iter1 != lista1->end()) && (iter2 != lista2->end())) {
		
		regNew = new RegistroDocFrec();
		
		reg1 = *iter1;
		reg2 = *iter2;
		
		if (reg1->getDocumento() == reg2->getDocumento()) {
			// si estan en el mismo documento
			regNew->setDocumento(reg1->getDocumento());
			((RegistroDocFrec *)regNew)->setFrecuencia(((RegistroDocFrec *)reg1)->getFrecuencia() + 
								  ((RegistroDocFrec *)reg2)->getFrecuencia());
			iter1++;
			iter2++;
		} else if (reg1->getDocumento() < reg2->getDocumento()) {
			//si el doc de 1, es menor que el doc de 2
			regNew->setDocumento(reg1->getDocumento());
			((RegistroDocFrec *)regNew)->setFrecuencia(((RegistroDocFrec *)reg1)->getFrecuencia());
			iter1++;
		} else if (reg1->getDocumento() > reg2->getDocumento()) {
			//si el doc de 2, es menor que el doc de 1
			regNew->setDocumento(reg2->getDocumento());
			((RegistroDocFrec *)regNew)->setFrecuencia(((RegistroDocFrec *)reg2)->getFrecuencia());
			iter2++;
		}
		resultado->push_back(regNew);
	}

	// chequeo si quedó algo en alguna de las listas

	// esto significa que se terminó la lista2 y seguimos con la 1
	if (iter1 != lista1->end()) {
		for (; iter1 != lista1->end(); iter1++) {
			regNew = new RegistroDocFrec();
			regNew->setDocumento((*iter1)->getDocumento());
			((RegistroDocFrec *)regNew)->setFrecuencia(((RegistroDocFrec *)(*iter1))->getFrecuencia());
			
			resultado->push_back(regNew);
		}
	}

	// esto significa que se terminó la lista1 y seguimos con la 2
	if (iter2 != lista2->end()) {
		for (; iter2 != lista2->end(); iter2++) {
			regNew = new RegistroDocFrec();
			regNew->setDocumento((*iter2)->getDocumento());
			((RegistroDocFrec *)regNew)->setFrecuencia(((RegistroDocFrec *)(*iter2))->getFrecuencia());
			
			resultado->push_back(regNew);
		}
	}

	return resultado;
}

// crea una lista de doc-peso, a partir de una lista de doc-frec
// la frecuencia qeu recibe es la cantidad de docs en la que esta
list<RegistroDistancias *> * Merger::transformarFrecuenciaEnPeso(list<RegistroDistancias *> * lista) {
	
	list<RegistroDistancias *> * resultado = new list<RegistroDistancias *>();
	list<RegistroDistancias *>::iterator iterador = lista->begin();
	RegistroDocPeso * regDocPeso;
	int frecuenciaTermino = lista->size();
	int nroDoc = 0;
	float pesoFalso = 0;
	float pesoVerdadero = 0;
	int frecTermDoc = 0;
	
	for(; iterador != lista->end(); iterador++) {
		regDocPeso = new RegistroDocPeso();
		
		frecTermDoc = ((RegistroDocFrec *)(*iterador))->getFrecuencia();
		nroDoc = ((RegistroDocFrec *)(*iterador))->getDocumento();
		
		pesoFalso = calcularPeso(frecTermDoc, frecuenciaTermino);
		
		regDocPeso->setDocumento(nroDoc);
		regDocPeso->setPeso(pesoFalso);
		
		pesoVerdadero = sqrt(pesoFalso / frecTermDoc) * ((float)frecTermDoc);
		
		vectorNormas[nroDoc - 1] += pow(pesoVerdadero, 2);
			
		delete (*iterador);
		
		resultado->push_back(regDocPeso);
	}
	
	delete lista;
	
	return resultado;
}

// el primer int es la frecuencia del termino en el documento
// el segundo int es la cantidad de documentos en la que está
float Merger::calcularPeso(int frecuenciaTerminoDoc, int frecuenciaTermino) {
	
	float nroDoc = (float)this->nroDocumentos;
	float frecTermino = (float)frecuenciaTermino;
	
	return (frecuenciaTerminoDoc * pow(log10(nroDoc / frecTermino), 2) );
}

void Merger::guardarNormaDocumentos() {
	// recorro el indice de documentos actualizando la norma (haciendo aca la raiz)
	for (int i = 0; i < this->nroDocumentos; i++) {
		inIndexDocs->escribirNormaRegistro(i, sqrt(vectorNormas[i])); 
	}
}
